<!doctype html>
<html>
    <head>
      <meta name="viewport" content="width=device-width">
      <title>Rustfmt</title>
      <link rel="stylesheet"
            href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"
            integrity="sha512-7e0rszTy0dKTIgYzCeBXpmycq0EOkwAvxZ0dv/LAtQfcXWCoaRhzs+v2Mn6of3jImxPazbYxiK0kpE/7wZ/UQA=="
            crossorigin="anonymous"
            referrerpolicy="no-referrer" />
      <link rel="stylesheet"
            href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/styles/github-gist.min.css"
            integrity="sha512-od7JLoOTxM8w/HSKGzP9Kexc20K9p/M2zxSWsd7H1e4Ctf+8SQFtCWEZnW5u6ul5ehSECa5QmOk9ju2nQMmlVA=="
            crossorigin="anonymous"
            referrerpolicy="no-referrer" />
      <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.2/marked.min.js"
              integrity="sha512-xeUh+KxNyTufZOje++oQHstlMQ8/rpyzPuM+gjMFYK3z5ILJGE7l2NvYL+XfliKURMpBIKKp1XoPN/qswlSMFA=="
              crossorigin="anonymous" 
              referrerpolicy="no-referrer"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"
              integrity="sha512-PwQ5+jgXxxprNGc80ycHE3spgj6TuDieHe/yTkbEJ+U5aol7dTupi/4VbwHHzlQVW77Vb0GLOIsiYigHgC5vcg=="
              crossorigin="anonymous"
              referrerpolicy="no-referrer"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/highlight.min.js"
              integrity="sha512-av6ZR84Ldk6j29DMhf6v0cssEhow1VROFLoKbX7wrvzZB++/nV8m0jXbYeWcHPEzSNONImx6zwBskCUT9AQidA=="
              crossorigin="anonymous"
              referrerpolicy="no-referrer"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"
              integrity="sha512-3BBFWr73Xrf8GRjO+0pl0cbVwESBvg3ovnuCXpoqOkC/mkt/hTkFtutUPrwRz8eLySYvy5v1daulkyUZYvH8jw=="
              crossorigin="anonymous"
              referrerpolicy="no-referrer"></script>
      <script src="https://unpkg.com/vue-async-computed@3.8.1"></script>
      <style>
        @media (max-width: 767px) {
          .markdown-body {
            padding: 15px;
          }

          #search {
            max-width: 85%;
          }
        }
        body {
          overflow: scroll;
        }
        .markdown-body {
          box-sizing: border-box;
          min-width: 200px;
          max-width: 980px;
          margin: 0 auto;
          padding: 45px;
        }
        #search {
          border: 1px solid #d1d5da;
          padding-left: 30px;
          overflow: hidden;
        }
        .searchCondition {
          display: flex;
          flex-wrap: wrap;
        }
        .searchCondition > div {
          margin-right: 30px;
        }
        .header-link {
          position: relative;
        }
        .header-link:hover::before {
          position: absolute;
          left: -2em;
          padding-right: 0.5em;
          content: '\2002\00a7\2002';
        }
      </style>
    </head>
    <body>
        <div id="app">
          <article class="markdown-body">
            <div class="searchCondition">
              <div>
                <form style="display:flex;">
                  <label for="search" style="margin-right: 3px;" >search:</label>
                  <div style="position: relative;">
                    <input id="search" placeholder="Search all options" v-model="searchCondition">
                    <svg style="position: absolute; left: 8px; top: 7px;" class="octicon octicon-search subnav-search-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
                      <path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"></path>
                    </svg>
                  </div>
                </form>
              </div>
              <div>
                  <label for="stable">stable: </label>
                  <input type="checkbox" id="stable" v-model="shouldStable">
              </div>
              <div>
                  <label for="viewVersion">version: </label>
                  <select name="viewVersion" id="viewVersion" v-model="viewVersion">
                    <option v-for="option in versionOptions" v-bind:value="option">
                      {{ option }}
                    </option>
                  </select>
              </div>
            </div>
            <div v-html="aboutHtml"></div>
            <div v-html="configurationAboutHtml"></div>
            <div v-html="outputHtml"></div>
          </article>
        </div>
        <script>
            const RustfmtTagsUrl = 'https://api.github.com/repos/rust-lang/rustfmt/tags';
            const RustfmtLatestUrl = 'https://api.github.com/repos/rust-lang/rustfmt/releases/latest';
            const UrlHash = window.location.hash.replace(/^#/, '');
            const queryParams = new URLSearchParams(window.location.search);
            const searchParam = queryParams.get('search');
            const searchTerm = null !== searchParam ? searchParam : '';
            const versionParam = queryParams.get('version');
            const parseVersionParam = (version) => {
              if (version === 'master') return 'master';
              if (version.startsWith('v')) return version;
              return `v${version}`;
            };
            const versionNumber = null !== versionParam ? parseVersionParam(versionParam) : 'master';
            new Vue({
              el: '#app',
              data: {
                aboutHtml: '',
                configurationAboutHtml: '',
                configurationDescriptions: [],
                searchCondition: searchTerm,
                shouldStable: false,
                viewVersion: versionNumber,
                oldViewVersion: undefined,
                versionOptions: ['master'],
                scrolledOnce: false,
              },
              asyncComputed: {
                async updateVersion() {
                  let latest;
                  try {
                    latest = (await axios.get(RustfmtLatestUrl)).data;
                  } catch(err) {
                      console.log(err);
                    return;
                  }
                  if (versionParam == null) {
                    this.viewVersion = latest.name;
                  }
                },
                async outputHtml() {
                  if (this.viewVersion !== this.oldViewVersion) {
                    const ConfigurationMdUrl =
                      `https://raw.githubusercontent.com/rust-lang/rustfmt/${this.viewVersion}/Configurations.md`;
                    let res;
                    try {
                      res = await axios.get(ConfigurationMdUrl).catch(e => { throw e });
                    } catch(e) {
                      this.handleReqFailure(e);
                      return;
                    }
                    const {
                      about,
                      configurationAbout,
                      configurationDescriptions
                    } = parseMarkdownAst(res.data);
                    this.aboutHtml = marked.parser(about);
                    this.configurationAboutHtml = marked.parser(configurationAbout);
                    this.configurationDescriptions = configurationDescriptions;
                    this.oldViewVersion = this.viewVersion;
                  }

                  const ast = this.configurationDescriptions
                      .filter(({ head, text, stable }) => {
                        if (text.includes(this.searchCondition) === false &&
                          head.includes(this.searchCondition) === false) {
                          return false;
                        }
                        return (this.shouldStable)
                          ? stable === true
                          : true;
                      })
                      .reduce((stack, { value }) => {
                        return stack.concat(value);
                      }, []);
                  ast.links = {};

                  queryParams.set('version', this.viewVersion);
                  queryParams.set('search', this.searchCondition);
                  const curUrl = window.location.pathname +
                    '?' + queryParams.toString() + window.location.hash;
                  history.pushState(null, '', curUrl);

                  const renderer = new marked.Renderer();
                  renderer.heading = function(text, level) {
                    const id = htmlToId(text);
                    return `<h${level}>
                              <a id="${id}" href="#${id}" name="${id}" class="header-link">${text}</a>
                            </h${level}>`;
                  };

                  return marked.parser(ast, {
                    highlight(code, lang) {
                      return hljs.highlight(lang ? lang : 'rust', code).value;
                    },
                    headerIds: true,
                    headerPrefix: '',
                    renderer,
                  });
                }
              },
              created: async function() {
                let tags;
                try {
                  tags = (await axios.get(RustfmtTagsUrl)).data;
                } catch(e) {
                  this.handleReqFailure(e);
                  return;
                }

                const excludedTagVersions = new Set(['v0.7', 'v0.8.1']);

                const tagOptions = tags
                  .map(tag => tag.name)
                  .filter(tag => tag.startsWith('v') && !excludedTagVersions.has(tag));
                this.versionOptions = this.versionOptions.concat(tagOptions);
              },
              updated() {
                if (UrlHash === '') return;
                this.$nextTick(() => {
                  const target = document.querySelector(`#${UrlHash}`);
                  if (target != null && !this.scrolledOnce) {
                    target.scrollIntoView(true);
                    this.scrolledOnce = true;
                  }
                });
              },
              methods: {
                handleReqFailure(e) {
                  if (e.response.status === 404) {
                    this.aboutHtml =
                      "<p>Failed to get configuration options for this version, please select the version from the dropdown above.</p>";
                  } else if (
                    e.response.status === 403 &&
                    e.response.headers["X-RateLimit-Remaining"] === 0
                  ) {
                    const resetDate = new Date(
                      e.response.headers['X-RateLimit-Reset'] * 1000
                    ).toLocaleString();
                    this.aboutHtml =
                      `<p>You have hit the GitHub API rate limit; documentation cannot be updated.` +
                      `<p>The rate limit will be reset at ${resetDate}.</p>`;
                  } else {
                    this.aboutHtml =
                      `<p>Encountered an error when fetching documentation data:</p>` +
                      `<pre><code>${e.response.data}</code></pre>` +
                      `<p>We would appreciate <a href="https://github.com/rust-lang/rustfmt/issues/new?template=bug_report.md">a bug report</a>.` +
                      `<p>Try refreshing the page.</p>`;
                  }
                }
              }
            });
            const extractDepthOnes = (ast) => {
              return ast.reduce((stack, next) => {
                if (next.depth === 1) {
                  stack.push([]);
                }
                const lastIndex = stack.length - 1;
                stack[lastIndex].push(next);
                return stack;
              }, []);
            }
            const extractDepthTwos = (ast) => {
              return ast.map((elem) => {
                return elem.reduce((stack, next) => {
                  if (next.depth === 2) {
                    stack.push([]);
                  }
                  const lastIndex = stack.length - 1;
                  stack[lastIndex].push(next);
                  return stack;
                },
                [[]]);
              });
            }
            const createHeadAndValue = (ast) => {
              return ast.map((elem) => {
                return elem.map((val) => {
                  return {
                    head: val[0].text,
                    value: val,
                    stable: val.some((elem) => {
                      return elem.type === "list" &&
                        !!elem.raw &&
                        elem.raw.includes("**Stable**: Yes");
                    }),
                    text: val.reduce((result, next) => {
                      return next.text != null
                        ? `${result} ${next.text}`
                        : result;
                    }, '')
                  }
                });
              })
            }
            const parseMarkdownAst = (rawMarkdown) => {
              const ast = marked.lexer(rawMarkdown);
              const depthOnes = extractDepthOnes(ast);
              const depthTwos = extractDepthTwos(depthOnes);
              const [
                abouts, configurations
              ] = createHeadAndValue(depthTwos);
              const about = abouts[0].value;
              about.links = {};
              const [
                configurationAbout, ...configurationDescriptions
              ] = configurations;
              configurationAbout.value.links = {};

              return {
                about,
                configurationAbout: configurationAbout.value,
                configurationDescriptions
              };
            }
            function htmlToId(text) {
              const tmpl = document.createElement('template');
              tmpl.innerHTML = text.trim();
              return encodeURIComponent(CSS.escape(tmpl.content.textContent));
            }
        </script>
    </body>
</html>
